// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: jhkim

#pragma once

#include "presto/quant/base/time.h"
#include "presto/quant/math/ewma.h"

namespace coin2::strategy::rmm_strat {

struct RmmPricerWindow {
  explicit RmmPricerWindow(int64_t window_size) {
    self_indexp_window.ResizeWindow(window_size);
    ref_indexp_window.ResizeWindow(window_size);
  }

  void UpdateIndex(int64_t timestamp, double indexp, bool is_ref) {
    if (is_ref) {
      ref_index_timestamp = timestamp;
      ref_indexp = indexp;
    } else {
      self_index_timestamp = timestamp;
      self_indexp = indexp;
    }

    // TODO(jhkim/hgpark): parametrize
    int64_t indexp_time_tolerance = 10e9;
    if (timestamp - ref_index_timestamp > indexp_time_tolerance ||
        timestamp - self_index_timestamp > indexp_time_tolerance) {
      // reject updating window
      return;
    }

    self_indexp_window.PushElement(timestamp, self_indexp);
    self_indexp_window.UpdateTimeOnly(timestamp);
    ref_indexp_window.PushElement(timestamp, ref_indexp);
    ref_indexp_window.UpdateTimeOnly(timestamp);
  }

  double GetSelfFairPrice(double ref_midp, double quote_multiplier = 1.0) const {
    // quote_multiplier = USDT/USD if quote is USDT
    // r1 - r2 = t1 - t2
    // t2 = t1 + r1 - r2
    double self_indexp_ma = self_indexp_window.average();
    double ref_indexp_ma = ref_indexp_window.average() * quote_multiplier;
    double indexp_diff_tolerance_bps = 10;
    double alpha = std::max(
        0.0,
        std::abs(self_indexp_ma - ref_indexp_ma) / (0.5 * (self_indexp_ma + ref_indexp_ma)) * 1e4 -
            indexp_diff_tolerance_bps);
    return ref_midp * quote_multiplier + std::exp(-alpha) * (self_indexp_ma - ref_indexp_ma);
  }

  bool Ready(
      int64_t timestamp,
      int64_t ref_book_timestamp,
      int64_t index_feed_valid_ns,
      int64_t book_feed_valid_ns) const {
    // TODO(jhkim/hgpark): fix arbitrary 10
    if (self_indexp_window.num_elements() <= 10 || self_indexp_window.average() <= 0) {
      return false;
    } else if (timestamp - ref_index_timestamp > index_feed_valid_ns) {
      return false;
    } else if (timestamp - self_index_timestamp > index_feed_valid_ns) {
      return false;
    } else if (timestamp - ref_book_timestamp > book_feed_valid_ns) {
      return false;
    } else {
      return true;
    }
  }
  int64_t self_index_timestamp = 0L;
  double self_indexp = 0;
  int64_t ref_index_timestamp = 0L;
  double ref_indexp = 0;

  presto::math::MovingAverageWindow<double> self_indexp_window;
  presto::math::MovingAverageWindow<double> ref_indexp_window;
};  // struct RmmPricerWindow

class TimeSampledMovingAverage : protected presto::math::MovingAverageWindow<double> {
 public:
  explicit TimeSampledMovingAverage(int64_t window_size)
      : MovingAverageWindow(window_size), sample_ts_(2'000'000'000) {
    presto::math::MovingAverageWindow<double>::ResizeWindow(window_size);
  }
  TimeSampledMovingAverage(int64_t window_size, int64_t sample_ts)
      : presto::math::MovingAverageWindow<double>(window_size), sample_ts_(sample_ts) {
    presto::math::MovingAverageWindow<double>::ResizeWindow(window_size);
  }

  void Update(int64_t timestamp, const double& value) {
    int64_t ts_group = timestamp / sample_ts_;
    DCHECK(ts_group >= last_sampled_ts_group_)
        << fmt::format("ts_group: {}, last_sampled_ts_group: {}", ts_group, last_sampled_ts_group_);
    if (ts_group != last_sampled_ts_group_) {
      last_sampled_ts_group_ = ts_group;
      presto::math::MovingAverageWindow<double>::Update(timestamp, value);
    }
  }

  double average() const { return presto::math::MovingAverageWindow<double>::average(); }
  int64_t num_elements() const { return presto::math::MovingAverageWindow<double>::num_elements(); }

 protected:
  int64_t sample_ts_{};
  int64_t last_sampled_ts_group_{0};

};  // class TimeSampledMovingAverage

}  // namespace coin2::strategy::rmm_strat
